package my.shadowsocks.common.encryption.impl;

import my.shadowsocks.common.encryption.AbstractCrypt;
import org.bouncycastle.crypto.StreamBlockCipher;
import org.bouncycastle.crypto.engines.CamelliaEngine;
import org.bouncycastle.crypto.modes.CFBBlockCipher;

import java.io.ByteArrayOutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.util.HashMap;
import java.util.Map;

/**
 * Camellia cipher implementation
 * @author fengbo
 */
public class CamelliaCrypt extends AbstractCrypt {

    private final static String CIPHER_CAMELLIA_128_CFB = "camellia-128-cfb";
    private final static String CIPHER_CAMELLIA_192_CFB = "camellia-192-cfb";
    private final static String CIPHER_CAMELLIA_256_CFB = "camellia-256-cfb";

    public static Map<String, String> getCiphers() {
        Map<String, String> ciphers = new HashMap<>(5);
        ciphers.put(CIPHER_CAMELLIA_128_CFB, CamelliaCrypt.class.getName());
        ciphers.put(CIPHER_CAMELLIA_192_CFB, CamelliaCrypt.class.getName());
        ciphers.put(CIPHER_CAMELLIA_256_CFB, CamelliaCrypt.class.getName());
        return ciphers;
    }

    public CamelliaCrypt(String name, String password) {
        super(name, password);
    }

    @Override
    public int getKeyLength() {
        switch (name) {
            case CIPHER_CAMELLIA_128_CFB:
                return 16;
            case CIPHER_CAMELLIA_192_CFB:
                return 24;
            case CIPHER_CAMELLIA_256_CFB:
                return 32;
            default:
                return 0;
        }
    }

    @Override
    protected StreamBlockCipher getCipher(boolean isEncrypted) throws InvalidAlgorithmParameterException {
        CamelliaEngine engine = new CamelliaEngine();
        switch (name) {
            case CIPHER_CAMELLIA_128_CFB:
                return new CFBBlockCipher(engine, getIVLength() * 8);
            case CIPHER_CAMELLIA_192_CFB:
                return new CFBBlockCipher(engine, getIVLength() * 8);
            case CIPHER_CAMELLIA_256_CFB:
                return new CFBBlockCipher(engine, getIVLength() * 8);
            default:
                throw new InvalidAlgorithmParameterException(name);
        }
    }

    @Override
    public int getIVLength() {
        return 16;
    }

    @Override
    protected void encrypt0(byte[] data, ByteArrayOutputStream stream) {
        int noBytesProcessed;
        byte[] buffer = new byte[data.length];

        noBytesProcessed = encCipher.processBytes(data, 0, data.length, buffer, 0);
        stream.write(buffer, 0, noBytesProcessed);
    }

    @Override
    protected void decrypt0(byte[] data, ByteArrayOutputStream stream) {
        int noBytesProcessed;
        byte[] buffer = new byte[data.length];

        noBytesProcessed = decCipher.processBytes(data, 0, data.length, buffer, 0);
        stream.write(buffer, 0, noBytesProcessed);
    }
}
